﻿#region Using Statements
using System;
using GameBase.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace StarFox.Graphics
{
    /// <summary>
    /// Performs bloom post-processing.
    /// </summary>
    internal class PostBloomProcessor : IDisposable
    {
        #region Fields

        Effect effect;

        // effect parameters
        EffectParameter pixelSize;
        EffectParameter halfPixelOffset;
        EffectParameter sceneMap;
        EffectParameter bloomMap;
        EffectParameter bloomThreshold;
        EffectParameter bloomIntensity;
        EffectParameter baseIntensity;
        EffectParameter bloomSaturation;
        EffectParameter baseSaturation;
        EffectParameter color;

        GraphicsDevice GraphicsDevice;
        QuadRenderer quad;
        RenderTarget2D auxRT_01, auxRT_02;

        Vector2 pixelSize_Full;
        Vector2 pixelSize_Half;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the bloom threshold.
        /// <para>Higher values produce smaller and better defined glows, while smaller ones produce a softer end result.</para>
        /// </summary>
        public float BloomThreshold { get; set; }

        /// <summary>
        /// Gets or sets the intensity of the original scene in the final combined result.
        /// </summary>
        public float BaseIntensity { get; set; }

        /// <summary>
        /// Gets or sets the intensity of the resulting bloom in the final combined result.
        /// </summary>
        public float BloomIntensity { get; set; }

        /// <summary>
        /// Gets or sets the color saturation of the original scene in the final combined result.
        /// </summary>
        public float BaseSaturation { get; set; }

        /// <summary>
        /// Gets or sets the color saturation of the resulting bloom in the final combined result.
        /// </summary>
        public float BloomSaturation { get; set; }

        /// <summary>
        /// Gets or sets the color correction factor.
        /// </summary>
        public Color Color { get; set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="effect">The bloom post-processing effect.</param>
        /// <param name="screenBorderTexture">The texture used for screen border fadeout.</param>
        public PostBloomProcessor(Effect effect, Texture2D screenBorderTexture)
        {
            this.effect = effect;
            GraphicsDevice = effect.GraphicsDevice;
            quad = new QuadRenderer(GraphicsDevice);

            // effect parameters
            pixelSize = effect.Parameters["PixelSize"];
            halfPixelOffset = effect.Parameters["HalfPixelOffset"];
            sceneMap = effect.Parameters["SceneMap"];
            bloomMap = effect.Parameters["BloomMap"];
            bloomThreshold = effect.Parameters["BloomThreshold"];
            bloomIntensity = effect.Parameters["BloomIntensity"];
            baseIntensity = effect.Parameters["BaseIntensity"];
            bloomSaturation = effect.Parameters["BloomSaturation"];
            baseSaturation = effect.Parameters["BaseSaturation"];
            color = effect.Parameters["Color"];
            effect.Parameters["FadeOutMap"].SetValue(screenBorderTexture);
                
            // downsampling... use a half-size rendertarget
            int width = GraphicsDevice.PresentationParameters.BackBufferWidth;
            int height = GraphicsDevice.PresentationParameters.BackBufferHeight;
            auxRT_01 = new RenderTarget2D(GraphicsDevice, width / 2, height / 2);
            auxRT_02 = new RenderTarget2D(GraphicsDevice, width / 2, height / 2);

            // half-pixel offset... use destination rendertarget pixel size
            pixelSize_Full = new Vector2(1.0f / width, 1.0f / height);
            pixelSize_Half = pixelSize_Full * 2;
        }

        #endregion

        #region Dispose

        // dispose flag
        bool isDisposed = false;

        /// <summary>
        /// Immediately releases all resources used by this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
        /// </summary>
        ~PostBloomProcessor()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by this object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    if (auxRT_01 != null) { auxRT_01.Dispose(); auxRT_01 = null; }
                    if (auxRT_02 != null) { auxRT_02.Dispose(); auxRT_02 = null; }
                }

                isDisposed = true;
            }
        }

        #endregion

        #region Apply

        /// <summary>
        /// Applies the bloom post-processing effect.
        /// </summary>
        /// <param name="inTexture">Texture2D used as processing input.</param>
        /// <param name="outRenderTarget">RenderTarget2D used as processing output.</param>
        public void Apply(Texture2D inTexture, RenderTarget2D outRenderTarget)
        {
            bloomThreshold.SetValue(BloomThreshold);
            bloomIntensity.SetValue(BloomIntensity);
            baseIntensity.SetValue(BaseIntensity);
            bloomSaturation.SetValue(BloomSaturation);
            baseSaturation.SetValue(BaseSaturation);
            color.SetValue(Color.ToVector3());

            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.None;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            for (int i = 0; i < effect.CurrentTechnique.Passes.Count; i++)
            {
                switch (i)
                {
                    case 0:
                        // downsample + bright pass filter
                        GraphicsDevice.SetRenderTarget(auxRT_01);
                        halfPixelOffset.SetValue(pixelSize_Half * 0.5f);
                        sceneMap.SetValue(inTexture);
                        break;
                    case 1:
                        // gaussian blur - horizontal pass
                        GraphicsDevice.SetRenderTarget(auxRT_02);
                        pixelSize.SetValue(pixelSize_Half);
                        halfPixelOffset.SetValue(pixelSize_Half * 0.5f);
                        bloomMap.SetValue(auxRT_01);
                        break;
                    case 2:
                        // gaussian blur - vertical pass
                        GraphicsDevice.SetRenderTarget(auxRT_01);
                        pixelSize.SetValue(pixelSize_Half);
                        halfPixelOffset.SetValue(pixelSize_Half * 0.5f);
                        bloomMap.SetValue(auxRT_02);
                        break;
                    case 3:
                        // final result
                        GraphicsDevice.SetRenderTarget(outRenderTarget);
                        halfPixelOffset.SetValue(pixelSize_Full * 0.5f);
                        bloomMap.SetValue(auxRT_01);
                        break;
                    default:
                        throw new InvalidOperationException("One or more effect passes not considered.");
                }

                GraphicsDevice.Clear(Color.Transparent);
                effect.CurrentTechnique.Passes[i].Apply();
                quad.Draw();
            }
        }

        #endregion
    }
}
